# -*- coding: utf-8 -*-
#@file name :tkGrid.py
#@author: Jamal Lahyani
#@contact: j.lahyani@gmail.com
#@date : 30 mars 2009
#@attention: Ce widget est encours de developpement.
#@version: 0.1b 
#@description:
#        Composant permetant d'afficher des informations sous forme de tableau
#        Le composant peut etre initialise en mode lecture seule ou lecture/ecriture
#
#@todo : Ajouter le scrolling
#        ajouter la fonction d'annulation d'une saisie
#        rendre la grille plus flexible par l ajout de parametres configurables
#        ajouter des champs pour les liste des titres des colonnes et lignes
#        rendre la grille redimmenssionable
#        ajouter une barre de status
#        ajouter les fonctions de lecture et de sauvgarde depuis un fichier
#        ajouter les fonctions d,impression et de conversion en fichier html
#        ajouter la fonction d export vers Excel, Word
#
#@help:
# Double-cliquer sur une case pour copier son contenu dans le presse-papier
# Cliquer sur le titre d une colone pour mettre toute la colonne en surbrillance
# Double-cliquer sur le titre d'une colonne pour copier le contenu de toute la colonne
# dans le presse-papier
# Cliquer sur le titre d une linge pour mettre toute la ligne en surbrillance
# Double-cliquer sur le titre d'une ligne pour copier le contenu de toute la ligne
# dans le presse-papier

from Tkinter import *


class tkGrid(Frame):

    class __Cell(Label,Entry):
        
        def __init__(self,parent,**args):
            keysList=args.keys()
            if ('type' in keysList):
                if(args['type']=='active'):
                    Entry.__init__(self,parent)
                else:
                    if(args['type']=='normal'):
                        Label.__init__(self,parent)
            else:
                Label.__init__(self,parent)
            self.master=parent
            self.__text=StringVar()
            self.__selected=False
            self.__text.set('')
            self.__oldFgColor=None
            self.__oldBgColor=None
            self.__width=10
            self.__height=1     
            self.__selFgColor='white'
            self.__selBgColor='blue'
            self.__fg='black'
            self.__bg='gray'
            self.__activeBgColor='blue'
            self.__activeFgColor='yellow' 
            self.row=args['row']
            self.column=args['column']
                    
            if ('fg' in keysList): self.__fg=args['fg']
            if ('bg' in keysList): self.__bg=args['bg']          
            if ('activeColor' in keysList): self.__selFgColor=args['activeColor']
            if ('selFgColor' in keysList): self.__selFgColor=args['selFgColor']
            if ('selBgColor' in keysList): self.__selBgColor=args['selBgColor'] 
            if ('height' in keysList): self.__height=args['height']
            if ('width' in keysList): self.__width=args['width']
            if ('text' in keysList): self.__text.set(args['text'])
            if ('selected' in keysList):
                self.__selected=args['selected']
                
            if (self.__selected): self.activate() 
            self.bind("<Double-Button-1>",self.select)
            self.bind("<Double-Button-1>",self.setActive,add='+')        
            self.bind("<Leave>",self.deselect)
                     
            self.grid(row=self.row,column=self.column)
            self.configure(textvariable=self.__text,background=self.__bg,\
                                                    foreground=self.__fg, width=self.__width)
            if ('type' in keysList and args['type']=='normal'):
                    self.configure(height=self.__height)            
            pass
        
        def isSelected(self):
            return self.__selected
            pass
        
        def setSelected(self,value):
            self.__selected=value
            pass
    
        def getText(self):
            return self.__text.get()
            pass
        
        def setText(self,text):
            self.__text.set(text)
            pass
        
        def setActive(self,evt): 
            self.clipboard_clear()
            self.clipboard_append(self.__text.get())
            self.__selected=True       
            pass       
            
        def select(self,evt):
            self.__oldFgColor=self.configure().get('background')[4]
            self.__oldBgColor=self.configure().get('foreground')[4]
            self.configure(background=self.__oldBgColor,foreground=self.__oldFgColor)            
            pass
    
        def deselect(self,evt):
            self.__oldFgColor=self.configure().get('foreground')[4]
            self.__oldBgColor=self.configure().get('background')[4]
            self.configure(background=self.__bg,foreground=self.__fg)
            pass

       
    def __init__(self,parent,**cnf):
        keyslist=cnf.keys()
        self.width=1        
        self.height=1       
        self.quitCommand=None
        self.checkCommands=None        
        self.x=0
        self.y=0
        self.type='normal',
        self.ArrayVar=None
        self.ColumnsHeaders=None
        self.title='tkGrid'        
        self.__freeze=False        
        if('title' in keyslist):
            self.title=cnf['title']
            del cnf['title']        
        if('x' in keyslist):
            self.left=cnf['x']
            del cnf['x']        
        if('y' in keyslist):
            self.top=cnf['y']
            del cnf['y']
        if('width' in keyslist):
            self.width=cnf['width']
            del cnf['width']        
        if('height' in keyslist):
            self.y=cnf['height']
            del cnf['height']    
        if('type' in keyslist):
            self.type=cnf['type']
            del cnf['type'] 
        if('quitcommand' in keyslist):
            self.quitCommand=cnf['quitcommand']
            del cnf['quitcommand']        
        if('checkcommands' in keyslist):
            self.checkcommands=cnf['checkcommands']
            del cnf['checkcommands']
        if('freeze' in keyslist):
            self.__freeze=cnf['freeze']       
            del cnf['freeze']
        if('arrayvar' in keyslist):
            self.ArrayVar=cnf['arrayvar']
            self.width=len(self.ArrayVar)
            self.height=len(self.ArrayVar[0])
            del cnf['arrayvar']
        if('columnsheaders' in keyslist):            
            self.ColumnsHeaders=cnf['columnsheaders']
            del cnf['columnsheaders']       
        
        Frame.__init__(self, parent,cnf)
        self.configure(border=2,relief='raised')           
        self.__lock=False
        self.parent=parent
        "self.parentTitle=self.parent.wm_title()"
        self.__x=self.left
        self.selectedCell=[-1,-1]
        self.__y=self.top 
        self.__Cells={}
        self.__textBackup={}
        self.__selectedColor='gray'
        self.__normalColor='white'
        self.activeRow=0
        self.activeColumn=2
        self.lastActiveColumn=2        
        self.lastActiveRow=2
        self.__x_inc=0
        self.__y_inc=0
        self.titleBar=Canvas(self,bg='black',width=10,height=20)
        self.titleBar.create_text(3,4, anchor=NW, text=self.title,\
                                    font =('Arial', 10, 'bold'),fill ='yellow')
        self.titleBar.grid(row=0,column=0,columnspan=self.width+2)
        self.titleBar.bind("<Button-1>",lambda evt ,r=True,dummy=self,l=self.width:self.__canMove(r,evt,l))
        self.titleBar.bind("<ButtonRelease-1>",lambda evt ,r=False,dummy=self,l=self.width:self.__canMove(r,evt,l))
        self.titleBar.bind("<Motion>",self.__move)
        self.bind("<Leave>",self.deactivate)
        self.bind("<Enter>",self.activate)
        
        self.__Cells[1,0]=self.__Cell(self,selected=False,row=1,column=0,
                                                 type='normal',bg='lightgray')        
        self.__Cells[1,0].unbind("<Enter>",None)
        self.__Cells[1,0].unbind("<Leave>",None)
        self.__Cells[1,0].unbind("<Button-1>",None)
        
        for i in range(2,self.height+2):
            self.__Cells[i,0]=self.__Cell(self,selected=False,row=i,column=0,\
                                        type='normal',text=str(i-1), bg=self.__normalColor)

        if (self.ColumnsHeaders==None):
            for i in range(2,self.width+2,1):
                self.__Cells[1,i]=self.__Cell(self,selected=False,row=1,column=i,\
                                                   type='normal',text=chr(63+i),bg='lightgray')
        else:
            for i in range(2,self.width+2,1):
                self.__Cells[1,i]=self.__Cell(self,selected=False,row=1,column=i,\
                                                   type='normal',text=self.ColumnsHeaders[i-2],bg='lightgray')
            self.activeColumn=i
            def CDBClicEventHandler(event,self=self,r=self.activeColumn):
                colonne=''
                for t in range(2,self.height+2,1):                    
                    colonne+=self.__Cells[r,t].getText()+'\n'
                return self.__sendColumn(event,colonne)            
            self.__Cells[1,i].bind( "<Double-Button-1>", CDBClicEventHandler,add='+') 
            self.__Cells[1,i].bind("<Button-1>",lambda dummy=self ,r=self.height,l=i,:
                                 self.selectColumns(r,dummy,l),add='+')
            
        for i in range(2,self.height+2,1):
            self.__Cells[i,1]=self.__Cell(self,selected=False,row=i,column=0,\
                                                 type='normal',text=str(i-1),bg='lightgray')
            self.activeRow=i
            def RDBClicEventHandler(event,self=self,r=self.activeRow):
                ligne=''
                for t in range(2,self.width+2,1):
                    ligne+=self.__Cells[t,r].getText()+'\n'
                return self.__sendRow(event,ligne)            
            self.__Cells[i,1].bind( "<Double-Button-1>", RDBClicEventHandler,add='+')          
            self.__Cells[i,1].bind("<Button-1>",lambda dummy=self ,r=i,l=self.width,:
                                 self.selectRows(r,dummy,l),add='+')
                    
        for i in range(2,self.width+2,1):
            row=self.ArrayVar[i-2]
            for j in range(2,self.height+2,1):                               
                self.__Cells[i,j]=self.__Cell(self,selected=False,row=j,column=i,type=self.type,\
                                                                text=row[j-2],bg='white')            
                self.__Cells[i,j].bind("<Button-1>",lambda evt ,r=j,l=i:
                                     self.selectCell(r,evt,l),add='+')
                self.__Cells[i,j].bind("<Enter>",lambda evt ,r=j,l=i:
                                     self.deselectCells(r,evt,l),add='+')
                self.__Cells[i,j].bind("<KeyPress>",lambda evt ,r=j,l=i:
                                     self.checkCell(r,evt,l),add='+')

        self.place_configure(x=self.__x,y=self.__y)
        self.configure(background='lightgray')
        self.update_idletasks()
        self.update()
        self.titleBar.configure(width=self.winfo_width())
        
    def kill():
        "self.parent.title(self.parentTitle)"
        self.__del__()
        pass
        self.close=Button(self,text='X',command=kill)
        self.close.configure(background='lightgray')
        self.close.place(x=self.winfo_width()-13,y=0)
    def XInHandler(evt):
        self.close.configure(background='yellow')
        pass
        self.close.bind("<Enter>",XInHandler)
    def XOutHandler(evt):
        self.close.configure(background='lightgray')
        pass
        self.close.bind("<Leave>",XOutHandler)
        if (self.__freeze==True): self.freeze()
        pass
    
    def getText(self,r,c):
        return self.__Cells[c+2,r+2].getText()
        pass
    
    def setText(self,r,c,value):
        self.__Cells[c+2,r+2].setText(value)
        pass
    
    def checkCell(self,r,evt,l):
        if(self.checkcommands!=None):
            try:
                if (self.checkcommands[l-2]!=None):
                    self.checkcommands[l-2](self.__Cells[l,r].getText())
            except: 0
        pass
    
    def deactivate(self,evt):
        if (self.__freeze==False):
            self.titleBar.configure(background='gray')
        pass

    def activate(self,evt):
        if (self.__freeze==False):        
            self.titleBar.configure(background='black')
            self.lift()
        pass
    
    def selectCell(self,r,evt,l):       
        self.selectedCell=[r,l]
        self.titleBar.delete(ALL)
        self.titleBar.create_text(3,4, anchor=NW, text=self.title+' | '+\
                                chr(65+l)+' - '+str(r+1)+ ' > '+self.__Cells[l,r].getText(),\
                                                font =('Arial', 10, 'bold'),fill ='yellow')
        pass

    def __sendRow(self,evt,row):
        self.clipboard_clear()
        self.clipboard_append(row)    
        pass

    def __sendColumn(self,evt,col):
        self.clipboard_clear()
        self.clipboard_append(col)
        pass

    def selectRows(self,r,evt,l):
        self.activeRow=r      
        for i in range(2,self.height+2,1):
            self.__Cells[self.lastActiveColumn,i].deselect(None)
            self.__Cells[self.lastActiveColumn,i].setSelected(False)  
            
        for i in range(2,l+2,1):
            self.__Cells[i,self.lastActiveRow].deselect(None)
            self.__Cells[i,self.lastActiveRow].setSelected(False)
            
        for i in range(2,l+2,1):
            self.__Cells[i,r].select(None)
            self.__Cells[i,r].setSelected(self.__Cells[i,r].isSelected())
        self.lastActiveRow=r
        self.lift()
        "self.parent.title(self.title)"        
        pass

    def selectColumns(self,r,evt,l):
        self.activeColumn=l  
        for i in range(2,self.width+1,1):
            self.__Cells[i,self.lastActiveRow].deselect(None)
            self.__Cells[i,self.lastActiveRow].setSelected(False) 
             
        for i in range(2,r+2,1):
            self.__Cells[self.lastActiveColumn,i].deselect(None)
            self.__Cells[self.lastActiveColumn,i].setSelected(False) 
                      
        for i in range(2,r+2,1):
            self.__Cells[l,i].select(None)
            self.__Cells[l,i].setSelected(self.__Cells[l,i].isSelected())
        self.lastActiveColumn=l
        "self.parent.title(self.title)"
        self.lift()
        pass
    
    def freeze(self):
        self.__freeze=True
        self.close.place(x=-0xFFFF)   
        pass
    
    def unfreeze(self):
        self.__freeze=False
        self.close.place(x=self.winfo_width()-21,y=0)
        pass
    
    def deselectCells(self,r,evt,l):
        for i in range(2,self.width+2,1):
            for j in range(2,self.height+2,1):                               
                self.__Cells[i,j].deselect(evt)
                self.__Cells[i,j].setSelected(False)
        pass
    def __canMove(self,r,evt,l):
        if (r):
            self.x_inc=self.__x-evt.x_root
            self.y_inc=self.__y-evt.y_root
            self.lift()
            self.__lock=True
        else:
            self.__lock=False
            self.x_inc=0
            self.y_inc=0
        "self.parent.title(self.title)"
       
        pass
        
    def __move(self,evt):
        if (self.__lock==True and self.__freeze==False \
            and evt.x_root<=(self.parent.winfo_width()+self.parent.winfo_x())\
            and (evt.y_root<=(self.parent.winfo_height()+self.parent.winfo_y()+30))):
             if(self.x_inc+evt.x_root<self.left):
                 self.__x=self.left

             else:
                  self.__x=self.x_inc+evt.x_root
             if(self.y_inc+evt.y_root<self.top):
                 self.__y=self.top
               
             else:
                 self.__y=self.y_inc+evt.y_root
             self.place_configure(x=self.__x,y=self.__y)                            
        pass

    
    def __del__(self):
        data=[]
        for i in range(2,self.width+2,1):
            row=[]
            for j in range(2,self.height+2,1):                               
                row.append(self.__Cells[i,j].getText())
            data.append(row)
        "self.parent.title(self.parentTitle)"
        if(self.quitCommand!=None):
            self.quitCommand(data)
        self.destroy()
        pass

if __name__ == '__main__':
    
    class App:
        
        def __init__(self):
            data1=[]                        
            for i in range(6):
                row=[]
                for j in range(12):
                    row.append('Cellule '+chr(65+i)+' '+str(j+1))
                data1.append(row)
            data2=[]
            data2.append(['Pommes','Tomates','Dattes'])
            data2.append(['25','3','200'])
            data2.append(['Kg','$','g'])

            self.top=Tk()
            self.top.geometry('600x400')
            self.top.resizable(0,0)
            self.top.title('tkGrid')
            self.top.configure(background='gray')
            Canvas(width=550,height=350,bg='white').place(x=50,y=50)
            
            # Commandes de verification des entres des colonnes
            # Les commandes vont etre ajoutees dans une liste
            # qu'on passe en paramettre.
            # Chaque colonne va etre associee a une fonction de validation
            
            def isNaN(s):
                if(s.isalpha()):
                    print 'Valeur correct'
                else:
                    print 'Valeur incorrect'
                pass

            def isNumber(s):                
                if(not s.isalpha()):
                    print 'Valeur correct'
                else:
                    print 'Valeur incorrect'
                pass
                pass
            
            def isUnit(s):                
                if (s in ['$','kg','Kg','kG','KG','g','G']):
                    print 'Valeur correct'
                else:
                    print 'Valeur incorrect'
                pass
            
            checkCommands=[isNaN,isNumber,isUnit]
            
            # Cette command va etre excecute a la destruction de la grille
            # Ceci va permettre de recuperer toutes les donnees de la grille
            # A utiliser seulement lorsque la grille est de type='active'
            
            def quitCommand(data):
                print '\nVoici le contenu de la grille:'
                for i in range(len(data)):
                    row=data[i]
                    line=''
                    for j in range(len(data[0])):
                        line+=row[j]+' '
                    print line
            columnsheaders=['Fruit','Quantité','Unité']
            self.grid2=tkGrid(
                                   self.top,
                                   x=50,y=50, # coin superieur gauche a ne pas franchir
                                   arrayvar=data2,
                                   quitcommand=quitCommand,
                                   checkcommands=checkCommands,
                                   columnsheaders=columnsheaders,
                                   freeze=True, 
                                   type='active', # widget=Entry
                                   title='Petite grille')
            # Recupere la valeur de la cellule 1,0
            print 'le contenu de la cellule 1,0 etait '+ self.grid2.getText(1,0)
            self.grid2.setText(1,0,'Kiwi') # Met a jour la cellule 1,0
          #  self.grid1=tkGrid(
           #                        self.top,
            #                       x=50,y=50,
             #                      arrayvar=data1,                                   
              #                     type='normal', # widget=Label
               #                    title='Grille large')
          #  "Méthode pour figer la grille"
            #self.grid2.freeze()
            #self.grid2.unfreeze()
            self.top.mainloop()

    root=App()